package besta.moon.command;

import java.sql.SQLException;
import java.util.ArrayList;

import besta.moon.engine.table.Column;
import besta.moon.sql.Expression;
import besta.moon.sql.datatype.Strings;
import besta.moon.MoonException;
import besta.moon.MoonConnection;
import besta.moon.MoonResultSet;
import besta.moon.statement.MoonStatement;
import besta.moon.MoonCode;
import besta.moon.MoonLogger;
import besta.moon.MoonCommand;
import besta.moon.engine.Table;
import besta.moon.engine.View;
import besta.moon.engine.index.IndexDescriptions;
import besta.moon.engine.store.MoonImpl;
import besta.moon.sql.expression.Expressions;
import besta.moon.util.MoonUtils;

public class CommandInsert extends MoonCommand {

    public boolean noColumns; 
    private CommandSelect cmdSel;

    private Table table;
    private long tableTimestamp;
    private int[] matrix; 

    public CommandInsert(MoonLogger log, String name) {
        super(log);
        this.name = name;
    }

    @Override
    public void addColumnExpression(Expression column) throws SQLException {
        if (columnExpressions.indexOf(column) >= 0) {
            throw MoonException.create(MoonCode.COL_DUPLICATE, column);
        }
        super.addColumnExpression(column);
    }

    public void addValues(Expressions values) {
        this.cmdSel = new CommandSelect(log, values);
    }

    public void addValues(CommandSelect cmdSel) {
        this.cmdSel = cmdSel;
    }

    private void compile(MoonConnection con) throws Exception {
        View tableView = con.getDatabase(false).getTableView(con, name);
        if (!(tableView instanceof Table)) {
            throw MoonException.create(MoonCode.VIEW_INSERT);
        }
        table = (Table) tableView;
        tableTimestamp = table.getTimestamp();
        cmdSel.compile(con);
        int count = table.columns.size();
        matrix = new int[count];
        if (noColumns) {
            columnExpressions.clear();
            for (int i = 0; i < count; i++) {
                matrix[i] = i;
            }
            if (count != cmdSel.columnExpressions.size()) {
                throw MoonException.create(MoonCode.COL_VAL_UNMATCH);
            }
        } else {
            for (int i = 0; i < count; i++) {
                matrix[i] = -1;
            }
            for (int c = 0; c < columnExpressions.size(); c++) {
                Expression sqlCol = columnExpressions.get(c);
                String sqlColName = sqlCol.getName();
                int idx = table.findColumnIdx(sqlColName);
                if (idx >= 0) {
                    matrix[idx] = c;
                } else {
                    throw MoonException.create(MoonCode.COL_MISSING, sqlColName);
                }
            }
            if (columnExpressions.size() != cmdSel.columnExpressions.size()) {
                throw MoonException.create(MoonCode.COL_VAL_UNMATCH);
            }
        }
    }

    @Override
    public void executeImpl(MoonConnection con, MoonStatement st) throws Exception {

        if (table == null || tableTimestamp != table.getTimestamp()) {
            compile(con);
        }

        final IndexDescriptions indexes = table.indexes;

        updateCount = 0;
        cmdSel.from.execute();
        cmdSel.beforeFirst();

        Strings keyColumnNames = null;
        ArrayList<Object> keys = null;
        boolean needGeneratedKeys = st.needGeneratedKeys();
        int generatedKeysType = 0;

        while (cmdSel.next()) {
            if (needGeneratedKeys) {
                keyColumnNames = new Strings();
                keys = new ArrayList<>();
                if (st.getGeneratedKeyNames() != null) {
                    generatedKeysType = 1;
                }
                if (st.getGeneratedKeyIndexes() != null) {
                    generatedKeysType = 2;
                }
            }
            MoonImpl store = table.getStoreInsert(con);
            for (int c = 0; c < matrix.length; c++) {
                Column column = table.columns.get(c);
                int idx = matrix[c];
                Expression valueExpress;
                if (idx >= 0) {
                    valueExpress = cmdSel.columnExpressions.get(idx);
                } else {
                    valueExpress = column.getDefaultValue(con);
                    if (needGeneratedKeys && generatedKeysType == 0 && valueExpress != Expression.NULL) {
                        keyColumnNames.add(column.getName());
                        keys.add(valueExpress.getObject());
                    }
                }
                if (needGeneratedKeys && generatedKeysType == 1) {
                    String[] keyNames = st.getGeneratedKeyNames();
                    for (int i = 0; i < keyNames.length; i++) {
                        if (column.getName().equalsIgnoreCase(keyNames[i])) {
                            keyColumnNames.add(column.getName());
                            keys.add(valueExpress.getObject());
                            break;
                        }
                    }
                }
                if (needGeneratedKeys && generatedKeysType == 2) {
                    int[] keyIndexes = st.getGeneratedKeyIndexes();
                    for (int i = 0; i < keyIndexes.length; i++) {
                        if (c + 1 == keyIndexes[i]) {
                            keyColumnNames.add(column.getName());
                            keys.add(valueExpress.getObject());
                            break;
                        }
                    }
                }
                store.writeExpression(valueExpress, column);
                for (int i = 0; i < indexes.size(); i++) {
                    indexes.get(i).writeExpression(c, valueExpress);
                }
            }
            store.writeFinsh(con);
            for (int i = 0; i < indexes.size(); i++) {
                indexes.get(i).writeFinish(con);
            }
            updateCount++;
            if (needGeneratedKeys) {
                Object[][] data = new Object[1][keys.size()];
                keys.toArray(data[0]);
                st.setGeneratedKeys(new MoonResultSet(st, MoonUtils.createMemoryCommandSelect(con, keyColumnNames.toArray(),
                        data)));
            }
        }
    }

}
